package com.yinxing.webapi.config;

import com.yinxing.framework.shiro.filter.RequiresAuthenticationFilter;
import com.yinxing.framework.shiro.filter.RequiresPermissionsFilter;
import com.yinxing.framework.shiro.filter.RequiresRolesFilter;
import com.yinxing.framework.shiro.filter.RequiresUserFilter;
import com.yinxing.framework.shiro.interceptor.MyMethodInterceptor;
import com.yinxing.framework.shiro.jwt.ShiroJwtTokenFilter;
import com.yinxing.framework.shiro.session.DefaultHeaderSessionManager;
import com.yinxing.framework.shiro.strategy.FirstExceptionStrategy;
import com.yinxing.webapi.shiro.AuthcListener;
import com.yinxing.webapi.shiro.JwtLoginRealm;
import com.yinxing.webapi.shiro.LoginRealm;
import org.apache.shiro.authc.AuthenticationListener;
import org.apache.shiro.authc.Authenticator;
import org.apache.shiro.authc.pam.AuthenticationStrategy;
import org.apache.shiro.authc.pam.FirstSuccessfulStrategy;
import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import org.apache.shiro.authz.ModularRealmAuthorizer;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.session.mgt.SessionFactory;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.session.mgt.SimpleSessionFactory;
import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;
import org.apache.shiro.session.mgt.eis.SessionDAO;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.mgt.DefaultWebSubjectFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.Filter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Configuration
public class ShiroConfig {

    /**
     * Session超时时间，单位为分钟（默认30分钟）
     */
    private final int globalSessionTimeout = 30;

    /**
     * 账户密码登录验证
     */
    @Bean
    public Realm loginRealm() {
        LoginRealm realm = new LoginRealm();
        realm.setAuthorizationCacheName(Ehcache2Config.AUTHORIZATION_CACHE);
        realm.setCacheManager(shiroCacheManager());
        return realm;
    }

    /**
     * jwt-token登录验证
     */
    @Bean
    public Realm jwtLoginRealm() {
        JwtLoginRealm realm = new JwtLoginRealm();
        realm.setCacheManager(shiroCacheManager());
        return realm;
    }

    /**
     * Ehcache缓存管理器
     */
    @Bean
    public org.apache.shiro.cache.CacheManager shiroCacheManager() {
        EhCacheManager shiroCacheManager = new EhCacheManager();
        shiroCacheManager.setCacheManagerConfigFile("classpath:" + Ehcache2Config.EHCACHE_CONFIG_NAME);
        return shiroCacheManager;
    }

    /**
     * Web安全管理器
     */
    @Bean
    public DefaultWebSecurityManager defaultWebSecurityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setSubjectDAO(new DefaultSubjectDAO());
        securityManager.setSubjectFactory(new DefaultWebSubjectFactory());
        securityManager.setRememberMeManager(new CookieRememberMeManager());
        securityManager.setSessionManager(sessionManager());
        securityManager.setAuthenticator(authenticator());
        securityManager.setAuthorizer(new ModularRealmAuthorizer());
        List<Realm> realms = new ArrayList<>();
        realms.add(loginRealm());
        realms.add(jwtLoginRealm());
        securityManager.setRealms(realms);
        securityManager.setCacheManager(shiroCacheManager());
        return securityManager;
    }

    /**
     * Shiro-Session管理器
     */
    @Bean
    public SessionManager sessionManager() {
        DefaultHeaderSessionManager manager = new DefaultHeaderSessionManager();
        // 加入缓存管理器
        manager.setCacheManager(shiroCacheManager());
        // 自定义SessionDao
        manager.setSessionDAO(sessionDAO());
        // 设置全局session超时时间
        manager.setGlobalSessionTimeout(TimeUnit.MINUTES.toMillis(globalSessionTimeout));
        // 删除过期的session
        manager.setDeleteInvalidSessions(true);
        // 是否定时检查session
        manager.setSessionValidationSchedulerEnabled(true);
        manager.setSessionFactory(sessionFactory());
        return manager;
    }

    /**
     * Shiro-Sesssion存储容器
     */
    @Bean
    public SessionDAO sessionDAO() {
        EnterpriseCacheSessionDAO cacheSessionDAO = new EnterpriseCacheSessionDAO();
        cacheSessionDAO.setCacheManager(shiroCacheManager());
        cacheSessionDAO.setActiveSessionsCacheName(Ehcache2Config.ACTIVE_SESSION_CACHE_NAME);
        return cacheSessionDAO;
    }

    /**
     * 自定义sessionFactory会话
     */
    @Bean
    public SessionFactory sessionFactory() {
        SimpleSessionFactory sessionFactory = new SimpleSessionFactory();
        return sessionFactory;
    }

    /**
     * Shiro拦截Filter
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean() {
        ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();
        filterFactoryBean.setSecurityManager(defaultWebSecurityManager());
        filterFactoryBean.setFilters(shiroFilter());
        filterFactoryBean.setFilterChainDefinitionMap(shiroFilterChainDefinition().getFilterChainMap());
        return filterFactoryBean;
    }

    /**
     * 拦截规则定义
     */
    @Bean
    public ShiroFilterChainDefinition shiroFilterChainDefinition() {
        DefaultShiroFilterChainDefinition cf = new DefaultShiroFilterChainDefinition();
        cf.addPathDefinition("/AppController/login", "anon");
        cf.addPathDefinition("/AppController/logout", "anon");
        //全部请求都需要jwt自动登录处理
        cf.addPathDefinition("/**", "JwtToken");
        cf.addPathDefinition("/pt-app/test", "NeedLogin,NeedRole[admin]");
        return cf;
    }

    /**
     * 自定义Shiro-Filter拦截器
     */
    public Map<String, Filter> shiroFilter() {
        Map<String, Filter> filterMap = new HashMap<>();
        filterMap.put("NeedLogin", new RequiresAuthenticationFilter());
        filterMap.put("NeedUser", new RequiresUserFilter());
        filterMap.put("NeedRole", new RequiresRolesFilter());
        filterMap.put("NeedPerm", new RequiresPermissionsFilter());
        filterMap.put("JwtToken", new ShiroJwtTokenFilter());
        return filterMap;
    }

    /**
     * 认证策略
     */
    @Bean
    public AuthenticationStrategy authenticationStrategy() {
        return new FirstSuccessfulStrategy();
    }

    /**
     * 登录与登出事件
     */
    @Bean
    public Authenticator authenticator() {
        ModularRealmAuthenticator authenticator = new ModularRealmAuthenticator();
        authenticator.setAuthenticationStrategy(authenticationStrategy());
        List<AuthenticationListener> listeners = new ArrayList<>();
        listeners.add(authcListener());
        authenticator.setAuthenticationListeners(listeners);
        authenticator.setAuthenticationStrategy(new FirstExceptionStrategy());
        return authenticator;
    }

    /**
     * 认证事件
     */
    @Bean
    public AuthenticationListener authcListener() {
        AuthcListener authcListener = new AuthcListener();
        return authcListener;
    }

    /**
     * Shiro注解拦截AOP
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        //重写了AnnotationsAuthorizingMethodInterceptor的权限拦截逻辑,我们抛出精细化异常信息.
        advisor.setAdvice(new MyMethodInterceptor());
        advisor.setSecurityManager(securityManager);
        return advisor;
    }

    /**
     * Shiro对象生命周期管理
     */
    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }
}
